All files / web/src/app/api/curriculum/[playerId]/attachments/[attachmentId]/review route.ts

0% Statements 0/141
0% Branches 0/1
0% Functions 0/1
0% Lines 0/141

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142                                                                                                                                                                                                                                                                                           
/**
 * API route for reviewing and correcting parsed worksheet results
 *
 * PATCH /api/curriculum/[playerId]/attachments/[attachmentId]/review
 *   - Submit user corrections to parsed problems
 *   - Updates the parsing result with corrections
 */

import { NextResponse } from 'next/server'
import { eq } from 'drizzle-orm'
import { z } from 'zod'
import { db } from '@/db'
import { practiceAttachments, type ParsingStatus } from '@/db/schema/practice-attachments'
import { withAuth } from '@/lib/auth/withAuth'
import { canPerformAction } from '@/lib/classroom'
import { getUserId } from '@/lib/viewer'
import {
  applyCorrections,
  computeParsingStats,
  ProblemCorrectionSchema,
} from '@/lib/worksheet-parsing'

/**
 * Request body schema for corrections
 */
const ReviewRequestSchema = z.object({
  corrections: z.array(ProblemCorrectionSchema).min(1),
  markAsReviewed: z.boolean().default(false),
})

/**
 * PATCH - Submit corrections to parsed problems
 */
export const PATCH = withAuth(async (request, { params }) => {
  try {
    const { playerId, attachmentId } = (await params) as { playerId: string; attachmentId: string }

    if (!playerId || !attachmentId) {
      return NextResponse.json({ error: 'Player ID and Attachment ID required' }, { status: 400 })
    }

    // Authorization check
    const userId = await getUserId()
    const canReview = await canPerformAction(userId, playerId, 'start-session')
    if (!canReview) {
      return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
    }

    // Parse request body
    const body = await request.json()
    const parseResult = ReviewRequestSchema.safeParse(body)
    if (!parseResult.success) {
      return NextResponse.json(
        {
          error: 'Invalid request body',
          details: parseResult.error.issues,
        },
        { status: 400 }
      )
    }

    const { corrections, markAsReviewed } = parseResult.data

    // Get attachment record
    const attachment = await db
      .select()
      .from(practiceAttachments)
      .where(eq(practiceAttachments.id, attachmentId))
      .get()

    if (!attachment) {
      return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })
    }

    if (attachment.playerId !== playerId) {
      return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })
    }

    // Check if we have parsing results to correct
    if (!attachment.rawParsingResult) {
      return NextResponse.json(
        {
          error: 'No parsing results to correct. Parse the worksheet first.',
        },
        { status: 400 }
      )
    }

    // Apply corrections to approved result (if exists) or raw result
    // This ensures corrections are cumulative
    const baseResult = attachment.approvedResult ?? attachment.rawParsingResult
    const correctedResult = applyCorrections(
      baseResult,
      corrections.map((c) => ({
        problemNumber: c.problemNumber,
        correctedTerms: c.correctedTerms ?? undefined,
        correctedStudentAnswer: c.correctedStudentAnswer ?? undefined,
        shouldExclude: c.shouldExclude,
        shouldRestore: c.shouldRestore,
      }))
    )

    // Compute new stats
    const stats = computeParsingStats(correctedResult)

    // Determine new status
    let newStatus: ParsingStatus = attachment.parsingStatus ?? 'needs_review'
    if (markAsReviewed) {
      // If user explicitly marks as reviewed, set to approved
      newStatus = 'approved'
    } else if (!correctedResult.needsReview) {
      // If all problems now have high confidence, auto-approve
      newStatus = 'approved'
    } else {
      // Still needs review
      newStatus = 'needs_review'
    }

    // Update database - store corrected result as approved result
    await db
      .update(practiceAttachments)
      .set({
        parsingStatus: newStatus,
        approvedResult: correctedResult,
        confidenceScore: correctedResult.overallConfidence,
        needsReview: correctedResult.needsReview,
      })
      .where(eq(practiceAttachments.id, attachmentId))

    return NextResponse.json({
      success: true,
      status: newStatus,
      result: correctedResult,
      stats,
      correctionsApplied: corrections.length,
    })
  } catch (error) {
    console.error('Error applying corrections:', error)
    return NextResponse.json({ error: 'Failed to apply corrections' }, { status: 500 })
  }
})